from PyCmpltrtok.common import sep
from copy import deepcopy

ALIVE = '*'
EMPTY = '-'
VALID_STATES = set([ALIVE, EMPTY])


class Grid:

    def __init__(self, height: int, width: int):
        self.height = height
        self.width = width
        self.rows = []
        for _ in range(self.height):
            self.rows.append([EMPTY] * self.width)

    def get(self, y: int, x: int) -> str:
        return self.rows[y % self.height][x % self.width]

    def set(self, y: int, x: int, state: str) -> None:
        assert state in VALID_STATES
        self.rows[y % self.height][x % self.width] = state

    def __str__(self):
        out = ''
        for y in range(self.height):
            for x in range(self.width):
                out += self.get(y, x)
            out += '\n'
        return out


def count_neighbors(y: int, x: int, get: Grid.get):
    n_ = get(y - 1, x + 0) # North
    ne = get(y - 1, x + 1) # Northeast
    e_ = get(y + 0, x + 1) # East
    se = get(y + 1, x + 1) # Southeast
    s_ = get(y + 1, x + 0) # South
    sw = get(y + 1, x - 1) # Southwest
    w_ = get(y + 0, x - 1) # West
    nw = get(y - 1, x - 1) # Northwest
    neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw]
    count = 0
    for state in neighbor_states:
        if state == ALIVE:
            count += 1
    return count


def game_logic(state: str, neighbors: int) -> str:
    assert state in VALID_STATES
    if ALIVE == state:
        if neighbors < 2:
            return EMPTY
        elif neighbors > 3:
            return EMPTY
    else:
        if 3 == neighbors:
            return ALIVE
    return state


def step_cell(y: int, x: int, get: Grid.get, set: Grid.set) -> None:
    state = get(y, x)
    neighbors = count_neighbors(y, x, get)
    next_state = game_logic(state, neighbors)
    set(y, x, next_state)


def simulate(grid: Grid) -> Grid:
    next_grid = Grid(grid.height, grid.width)
    for y in range(grid.height):
        for x in range(grid.width):
            step_cell(y, x, grid.get, next_grid.set)
    return next_grid


class ColumnPrinter:

    def __init__(self):
        self.out = ''

    def __str__(self):
        return self.out + '\n'

    def append(self, grid) -> None:
        grid_str = f'{grid}'
        if -1 == self.out.find('\n'):
            self.out = grid_str
        else:
            self.out = '\n'.join([ori + '|' + new for ori, new in zip(self.out.split('\n'), grid_str.split('\n')) if ori and new])


if '__main__' == __name__:

    def _main():
        sep('Test Grid class')
        grid = Grid(5, 9)
        grid.set(0, 3, ALIVE)
        grid.set(1, 4, ALIVE)
        grid.set(2, 2, ALIVE)
        grid.set(2, 3, ALIVE)
        grid.set(2, 4, ALIVE)
        print(grid)

        sep('Test ColumnPrinter')
        printer = ColumnPrinter()

        # 0
        print(printer)

        # x1
        printer.append(grid)
        print(printer)

        # x2
        printer.append(grid)
        print(printer)

        # x3
        printer.append(grid)
        print(printer)

        sep('Run it')

        def run_it(grid, n):
            g = deepcopy(grid)
            columns = ColumnPrinter()
            for _ in range(n):
                columns.append(g)
                g = simulate(g)
            print(columns)
            return g

        sep('x5')
        g = run_it(grid, 5)
        sep('x5')
        g = run_it(g, 5)
        sep('x10')
        g = run_it(g, 11)

    _main()
